jnunemaker-httparty 0.2.6 → 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
data/History CHANGED
@@ -1,3 +1,12 @@
1
+ == 0.2.7 2009-01-28
2
+ * 2 minor fixes, 2 minor enhancements, 2 major enhancements
3
+ * fixed undefined method add_node for nil class error that occasionally happened (juliocesar)
4
+ * Handle nil or unexpected values better when typecasting. (Brian Landau)
5
+ * More robust handling of mime types (Alex Vollmer)
6
+ * Fixed support for specifying headers and added support for basic auth to CLI. (Alex Vollmer)
7
+ * Added first class response object that includes original body and status code (Alex Vollmer)
8
+ * Now parsing all response types as some non-200 responses provide important information, this means no more exception raising (Alex Vollmer)
9
+
1
10
  == 0.2.6 2009-01-05
2
11
  * 1 minor bug fix
3
12
  * added explicit require of time as Time#parse failed outside of rails (willcodeforfoo)
data/Manifest CHANGED
@@ -11,6 +11,7 @@ httparty.gemspec
11
11
  lib/core_extensions.rb
12
12
  lib/httparty/exceptions.rb
13
13
  lib/httparty/request.rb
14
+ lib/httparty/response.rb
14
15
  lib/httparty/version.rb
15
16
  lib/httparty.rb
16
17
  lib/module_level_inheritable_attributes.rb
@@ -21,9 +22,11 @@ README
21
22
  setup.rb
22
23
  spec/as_buggery_spec.rb
23
24
  spec/fixtures/delicious.xml
25
+ spec/fixtures/empty.xml
24
26
  spec/fixtures/google.html
25
27
  spec/fixtures/twitter.json
26
28
  spec/fixtures/twitter.xml
29
+ spec/fixtures/undefined_method_add_node_for_nil.xml
27
30
  spec/httparty/request_spec.rb
28
31
  spec/httparty_spec.rb
29
32
  spec/spec.opts
data/bin/httparty CHANGED
@@ -10,26 +10,30 @@ require "httparty"
10
10
  opts = {
11
11
  :action => :get,
12
12
  :headers => {},
13
- :keep_body => true,
14
- :verbose => false,
15
- :pretty_print => false
13
+ :verbose => false
16
14
  }
17
15
 
16
+ def die(msg)
17
+ STDERR.puts(msg)
18
+ exit 1
19
+ end
20
+
18
21
  OptionParser.new do |o|
19
22
  o.banner = "USAGE: #{$0} [options] [url]"
23
+
20
24
  o.on("-f",
21
25
  "--format [FORMAT]",
22
- "Body format: plain, json or xml") do |f|
23
- opts[:format] = f.downcase.to_sym
24
- end
25
- o.on("-r", "--ruby", "Dump output in Ruby pretty-print format") do |r|
26
- opts[:pretty_print] = true
26
+ "Output format to use instead of pretty-print ruby: " +
27
+ "plain, json or xml") do |f|
28
+ opts[:output_format] = f.downcase.to_sym
27
29
  end
30
+
28
31
  o.on("-a",
29
32
  "--action [ACTION]",
30
33
  "HTTP action: get (default), post, put or delete") do |a|
31
34
  opts[:action] = a.downcase.to_sym
32
35
  end
36
+
33
37
  o.on("-d",
34
38
  "--data [BODY]",
35
39
  "Data to put in request body (prefix with '@' for file)") do |d|
@@ -39,13 +43,23 @@ OptionParser.new do |o|
39
43
  opts[:data] = d
40
44
  end
41
45
  end
46
+
42
47
  o.on("-H", "--header [NAME=VALUE]", "Additional HTTP headers in NAME=VALUE form") do |h|
43
- name, value = h.split('=')
44
- opts[:headers][name] = value
48
+ die "Invalid header specification, should be Name:Value" unless h =~ /.+:.+/
49
+ name, value = h.split(':')
50
+ opts[:headers][name.strip] = value.strip
45
51
  end
52
+
46
53
  o.on("-v", "--verbose", "If set, print verbose output") do |v|
47
54
  opts[:verbose] = true
48
55
  end
56
+
57
+ o.on("-u", "--user [CREDS]", "Use basic authentication. Value should be user:password") do |u|
58
+ die "Invalid credentials format. Must be user:password" unless u =~ /.+:.+/
59
+ user, password = u.split(':')
60
+ opts[:basic_auth] = { :username => user, :password => password }
61
+ end
62
+
49
63
  o.on("-h", "--help", "Show help documentation") do |h|
50
64
  puts o
51
65
  exit
@@ -76,28 +90,25 @@ module REXML
76
90
  REXML::Formatters::Default.new( ie_hack )
77
91
  end
78
92
  formatter.write( self, output )
79
- end
93
+ end
80
94
  end
81
95
  end
82
96
 
83
- if opts[:pretty_print] || opts[:format].nil?
84
- pp HTTParty.send(opts[:action], ARGV.first, opts)
97
+ if opts[:output_format].nil?
98
+ response = HTTParty.send(opts[:action], ARGV.first, opts)
99
+ puts "Status: #{response.code}"
100
+ pp response
85
101
  else
86
- print_format = opts[:format]
87
- opts.merge!(:format => :plain) if opts[:format]
102
+ print_format = opts[:output_format]
88
103
  response = HTTParty.send(opts[:action], ARGV.first, opts)
89
-
90
- if print_format.nil?
91
- pp response
104
+ puts "Status: #{response.code}"
105
+ case opts[:output_format]
106
+ when :json
107
+ puts JSON.pretty_generate(response)
108
+ when :xml
109
+ REXML::Document.new(response.body).write(STDOUT, 2)
110
+ puts
92
111
  else
93
- case print_format
94
- when :json
95
- puts JSON.pretty_generate(JSON.parse(response))
96
- when :xml
97
- REXML::Document.new(response).write(STDOUT, 2)
98
- puts
99
- else
100
- puts response
101
- end
112
+ puts response
102
113
  end
103
114
  end
data/httparty.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{httparty}
5
- s.version = "0.2.6"
5
+ s.version = "0.2.7"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["John Nunemaker"]
9
- s.date = %q{2009-01-05}
9
+ s.date = %q{2009-01-28}
10
10
  s.default_executable = %q{httparty}
11
11
  s.description = %q{Makes http fun! Also, makes consuming restful web services dead easy.}
12
12
  s.email = %q{nunemaker@gmail.com}
@@ -115,10 +115,10 @@ class REXMLUtilityNode
115
115
  self.typecasts["datetime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
116
116
  self.typecasts["date"] = lambda{|v| v.nil? ? nil : Date.parse(v)}
117
117
  self.typecasts["dateTime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
118
- self.typecasts["decimal"] = lambda{|v| BigDecimal(v)}
118
+ self.typecasts["decimal"] = lambda{|v| v.nil? ? nil : BigDecimal(v.to_s)}
119
119
  self.typecasts["double"] = lambda{|v| v.nil? ? nil : v.to_f}
120
120
  self.typecasts["float"] = lambda{|v| v.nil? ? nil : v.to_f}
121
- self.typecasts["symbol"] = lambda{|v| v.to_sym}
121
+ self.typecasts["symbol"] = lambda{|v| v.nil? ? nil : v.to_sym}
122
122
  self.typecasts["string"] = lambda{|v| v.to_s}
123
123
  self.typecasts["yaml"] = lambda{|v| v.nil? ? nil : YAML.load(v)}
124
124
  self.typecasts["base64Binary"] = lambda{|v| v.unpack('m').first }
@@ -280,7 +280,7 @@ class ToHashParser
280
280
  stack.last.add_node(temp)
281
281
  end
282
282
  when :text, :cdata
283
- stack.last.add_node(event[1]) unless event[1].strip.length == 0
283
+ stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty?
284
284
  end
285
285
  end
286
286
  stack.pop.to_hash
@@ -20,9 +20,9 @@ module HTTParty
20
20
  end
21
21
 
22
22
  def uri
23
- uri = path.relative? ? URI.parse("#{options[:base_uri]}#{path}") : path
24
- uri.query = query_string(uri)
25
- uri
23
+ new_uri = path.relative? ? URI.parse("#{options[:base_uri]}#{path}") : path
24
+ new_uri.query = query_string(new_uri)
25
+ new_uri
26
26
  end
27
27
 
28
28
  def format
@@ -31,32 +31,41 @@ module HTTParty
31
31
 
32
32
  def perform
33
33
  validate!
34
- handle_response!(get_response(uri))
34
+ setup_raw_request
35
+ handle_response!(get_response)
35
36
  end
36
37
 
37
38
  private
38
- def http(uri) #:nodoc:
39
+ def http #:nodoc:
39
40
  http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
40
41
  http.use_ssl = (uri.port == 443)
41
42
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
42
43
  http
43
44
  end
44
-
45
- def get_response(uri) #:nodoc:
46
- request = http_method.new(uri.request_uri)
45
+
46
+ def configure_basic_auth
47
+ @raw_request.basic_auth(options[:basic_auth][:username], options[:basic_auth][:password])
48
+ end
49
+
50
+ def setup_raw_request
51
+ @raw_request = http_method.new(uri.request_uri)
47
52
 
48
53
  if post? && options[:query]
49
- request.set_form_data(options[:query])
54
+ @raw_request.set_form_data(options[:query])
50
55
  end
51
56
 
52
- request.body = options[:body].is_a?(Hash) ? options[:body].to_params : options[:body] unless options[:body].blank?
53
- request.initialize_http_header options[:headers]
57
+ @raw_request.body = options[:body].is_a?(Hash) ? options[:body].to_params : options[:body] unless options[:body].blank?
58
+ @raw_request.initialize_http_header options[:headers]
54
59
 
55
- if options[:basic_auth]
56
- request.basic_auth(options[:basic_auth][:username], options[:basic_auth][:password])
57
- end
58
-
59
- response = http(uri).request(request)
60
+ configure_basic_auth if options[:basic_auth]
61
+ end
62
+
63
+ def perform_actual_request
64
+ http.request(@raw_request)
65
+ end
66
+
67
+ def get_response #:nodoc:
68
+ response = perform_actual_request
60
69
  options[:format] ||= format_from_mimetype(response['content-type'])
61
70
  response
62
71
  end
@@ -78,36 +87,33 @@ module HTTParty
78
87
  # Raises exception Net::XXX (http error code) if an http error occured
79
88
  def handle_response!(response) #:nodoc:
80
89
  case response
81
- when Net::HTTPSuccess
82
- parse_response(response.body)
83
- when Net::HTTPRedirection
84
- options[:limit] -= 1
85
- self.path = response['location']
86
- perform
87
- else
88
- response.instance_eval { class << self; attr_accessor :body_parsed; end }
89
- begin; response.body_parsed = parse_response(response.body); rescue; end
90
- response.error! # raises exception corresponding to http error Net::XXX
91
- end
90
+ when Net::HTTPRedirection
91
+ options[:limit] -= 1
92
+ self.path = response['location']
93
+ perform
94
+ else
95
+ parsed_response = parse_response(response.body)
96
+ Response.new(parsed_response, response.body, response.code)
97
+ end
92
98
  end
93
99
 
94
100
  def parse_response(body) #:nodoc:
95
101
  return nil if body.nil? or body.empty?
96
102
  case format
97
- when :xml
98
- ToHashParser.from_xml(body)
99
- when :json
100
- JSON.parse(body)
101
- else
102
- body
103
- end
103
+ when :xml
104
+ ToHashParser.from_xml(body)
105
+ when :json
106
+ JSON.parse(body)
107
+ else
108
+ body
109
+ end
104
110
  end
105
111
 
106
112
  # Uses the HTTP Content-Type header to determine the format of the response
107
113
  # It compares the MIME type returned to the types stored in the AllowedFormats hash
108
114
  def format_from_mimetype(mimetype) #:nodoc:
109
115
  return nil if mimetype.nil?
110
- AllowedFormats.each { |k, v| return k if mimetype.include?(v) }
116
+ AllowedFormats.each { |k, v| return v if mimetype.include?(k) }
111
117
  end
112
118
 
113
119
  def validate! #:nodoc:
@@ -1,3 +1,3 @@
1
1
  module HTTParty
2
- Version = '0.2.6'
2
+ Version = '0.2.7'
3
3
  end
data/lib/httparty.rb CHANGED
@@ -9,8 +9,17 @@ require 'json'
9
9
  require 'module_level_inheritable_attributes'
10
10
  require 'core_extensions'
11
11
 
12
- module HTTParty
13
- AllowedFormats = {:xml => 'text/xml', :json => 'application/json', :html => 'text/html'}
12
+ module HTTParty
13
+
14
+ AllowedFormats = {
15
+ 'text/xml' => :xml,
16
+ 'application/xml' => :xml,
17
+ 'application/json' => :json,
18
+ 'text/json' => :json,
19
+ 'application/javascript' => :json,
20
+ 'text/javascript' => :json,
21
+ 'text/html' => :html
22
+ } unless defined?(AllowedFormats)
14
23
 
15
24
  def self.included(base)
16
25
  base.extend ClassMethods
@@ -51,7 +60,7 @@ module HTTParty
51
60
  end
52
61
 
53
62
  def format(f)
54
- raise UnsupportedFormat, "Must be one of: #{AllowedFormats.keys.join(', ')}" unless AllowedFormats.key?(f)
63
+ raise UnsupportedFormat, "Must be one of: #{AllowedFormats.values.join(', ')}" unless AllowedFormats.value?(f)
55
64
  default_options[:format] = f
56
65
  end
57
66
 
@@ -109,4 +118,5 @@ module HTTParty
109
118
  end
110
119
 
111
120
  require 'httparty/exceptions'
112
- require 'httparty/request'
121
+ require 'httparty/request'
122
+ require 'httparty/response'
@@ -13,11 +13,57 @@ describe HTTParty::Request do
13
13
 
14
14
  describe 'http' do
15
15
  it "should use ssl for port 443" do
16
- @request.send(:http, URI.parse('https://api.foo.com/v1:443')).use_ssl?.should == true
16
+ request = HTTParty::Request.new(Net::HTTP::Get, 'https://api.foo.com/v1:443')
17
+ request.send(:http).use_ssl?.should == true
17
18
  end
18
19
 
19
20
  it 'should not use ssl for port 80' do
20
- @request.send(:http, URI.parse('http://foobar.com')).use_ssl?.should == false
21
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://foobar.com')
22
+ @request.send(:http).use_ssl?.should == false
23
+ end
24
+
25
+ it "should use basic auth when configured" do
26
+ @request.options[:basic_auth] = {:username => 'foobar', :password => 'secret'}
27
+ @request.send(:setup_raw_request)
28
+ @request.instance_variable_get(:@raw_request)['authorization'].should_not be_nil
29
+ end
30
+ end
31
+
32
+ describe '#format_from_mimetype' do
33
+ it 'should handle text/xml' do
34
+ ["text/xml", "text/xml; charset=iso8859-1"].each do |ct|
35
+ @request.send(:format_from_mimetype, ct).should == :xml
36
+ end
37
+ end
38
+
39
+ it 'should handle application/xml' do
40
+ ["application/xml", "application/xml; charset=iso8859-1"].each do |ct|
41
+ @request.send(:format_from_mimetype, ct).should == :xml
42
+ end
43
+ end
44
+
45
+ it 'should handle text/json' do
46
+ ["text/json", "text/json; charset=iso8859-1"].each do |ct|
47
+ @request.send(:format_from_mimetype, ct).should == :json
48
+ end
49
+ end
50
+
51
+ it 'should handle application/json' do
52
+ ["application/json", "application/json; charset=iso8859-1"].each do |ct|
53
+ @request.send(:format_from_mimetype, ct).should == :json
54
+ end
55
+ end
56
+
57
+ it 'should handle text/javascript' do
58
+ ["text/javascript", "text/javascript; charset=iso8859-1"].each do |ct|
59
+ @request.send(:format_from_mimetype, ct).should == :json
60
+ end
61
+ end
62
+
63
+ it 'should handle application/javascript' do
64
+ ["application/javascript", "application/javascript; charset=iso8859-1"].each do |ct|
65
+ @request.send(:format_from_mimetype, ct).should == :json
66
+ end
21
67
  end
22
68
  end
23
69
 
@@ -33,6 +79,32 @@ describe HTTParty::Request do
33
79
  @request.options[:format] = :json
34
80
  @request.send(:parse_response, json).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
35
81
  end
82
+
83
+ describe 'with non-200 responses' do
84
+
85
+ it 'should return a valid object for 4xx response' do
86
+ http_response = Net::HTTPUnauthorized.new('1.1', 401, '')
87
+ http_response.stub!(:body).and_return('<foo><bar>yes</bar></foo>')
88
+
89
+ @request.should_receive(:get_response).and_return(http_response)
90
+ resp = @request.perform
91
+ resp.code.should == 401
92
+ resp.body.should == "<foo><bar>yes</bar></foo>"
93
+ resp['foo']['bar'].should == "yes"
94
+ end
95
+
96
+ it 'should return a valid object for 5xx response' do
97
+ http_response = Net::HTTPUnauthorized.new('1.1', 500, '')
98
+ http_response.stub!(:body).and_return('<foo><bar>error</bar></foo>')
99
+
100
+ @request.should_receive(:get_response).and_return(http_response)
101
+ resp = @request.perform
102
+ resp.code.should == 500
103
+ resp.body.should == "<foo><bar>error</bar></foo>"
104
+ resp['foo']['bar'].should == "error"
105
+ end
106
+
107
+ end
36
108
  end
37
109
 
38
110
  it "should not attempt to parse empty responses" do
@@ -41,7 +113,7 @@ describe HTTParty::Request do
41
113
  response = Net::HTTPNoContent.new("1.1", 204, "No content for you")
42
114
  response.stub!(:body).and_return(nil)
43
115
  http.stub!(:request).and_return(response)
44
-
116
+
45
117
  @request.options[:format] = :xml
46
118
  @request.perform.should be_nil
47
119
 
@@ -173,5 +173,17 @@ describe HTTParty do
173
173
  "location" => nil
174
174
  }
175
175
  end
176
+
177
+ it "should not get undefined method add_node for nil class for the following xml" do
178
+ stub_http_response_with('undefined_method_add_node_for_nil.xml')
179
+ result = HTTParty.get('http://foobar.com')
180
+ result.should == {"Entities"=>{"href"=>"https://s3-sandbox.parature.com/api/v1/5578/5633/Account", "results"=>"0", "total"=>"0", "page_size"=>"25", "page"=>"1"}}
181
+ end
182
+
183
+ it "should parse empty response fine" do
184
+ stub_http_response_with('empty.xml')
185
+ result = HTTParty.get('http://foobar.com')
186
+ result.should == nil
187
+ end
176
188
  end
177
189
  end
data/spec/spec_helper.rb CHANGED
@@ -10,15 +10,12 @@ end
10
10
  def stub_http_response_with(filename)
11
11
  format = filename.split('.').last.intern
12
12
  data = file_fixture(filename)
13
- http = Net::HTTP.new('localhost', 80)
14
-
13
+
15
14
  response = Net::HTTPOK.new("1.1", 200, "Content for you")
16
15
  response.stub!(:body).and_return(data)
17
- http.stub!(:request).and_return(response)
18
-
19
- http_request = HTTParty::Request.new(Net::HTTP::Get, '')
20
- http_request.stub!(:get_response).and_return(response)
21
- http_request.stub!(:format).and_return(format)
22
-
16
+
17
+ http_request = HTTParty::Request.new(Net::HTTP::Get, 'http://localhost', :format => format)
18
+ http_request.stub!(:perform_actual_request).and_return(response)
19
+
23
20
  HTTParty::Request.should_receive(:new).and_return(http_request)
24
21
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jnunemaker-httparty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-05 00:00:00 -08:00
12
+ date: 2009-01-28 00:00:00 -08:00
13
13
  default_executable: httparty
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency