nestful 0.0.8 → 1.0.0.pre

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.
@@ -0,0 +1,42 @@
1
+ module Nestful
2
+ class Endpoint
3
+ def self.[](url)
4
+ self.new(url)
5
+ end
6
+
7
+ attr_reader :url
8
+
9
+ def initialize(url, options = {})
10
+ @url = url
11
+ @options = options
12
+ end
13
+
14
+ def [](suburl)
15
+ return self if suburl.nil?
16
+ suburl = suburl.to_s
17
+ base = url
18
+ base += "/" unless base =~ /\/$/
19
+ self.class.new(URI.join(base, suburl).to_s, @options)
20
+ end
21
+
22
+ def get(params = {}, options = {})
23
+ request(options.merge(:method => :get, :params => params))
24
+ end
25
+
26
+ def put(params = {}, options = {})
27
+ request(options.merge(:method => :put, :params => params))
28
+ end
29
+
30
+ def post(params = {}, options = {})
31
+ request(options.merge(:method => :post, :params => params))
32
+ end
33
+
34
+ def delete(params = {}, options = {})
35
+ request(options.merge(:method => :delete, :params => params))
36
+ end
37
+
38
+ def request(options = {})
39
+ Request.new(url, options.merge(@options)).execute
40
+ end
41
+ end
42
+ end
@@ -56,7 +56,7 @@ module Nestful
56
56
 
57
57
  # 410 Gone
58
58
  class ResourceGone < ClientError; end # :nodoc:
59
-
59
+
60
60
  # 422 Invalid
61
61
  class ResourceInvalid < ClientError; end # :nodoc:
62
62
 
@@ -4,11 +4,11 @@ module Nestful
4
4
  def mime_type
5
5
  'application/x-www-form-urlencoded'
6
6
  end
7
-
7
+
8
8
  def encode(params, options = nil)
9
- params.to_param
9
+ Helpers.to_param(params)
10
10
  end
11
-
11
+
12
12
  def decode(body)
13
13
  body
14
14
  end
@@ -1,24 +1,25 @@
1
- require 'yaml'
2
- require 'active_support/json'
1
+ begin
2
+ require 'json'
3
+ rescue LoadError => e
4
+ require 'json/pure'
5
+ end
3
6
 
4
7
  module Nestful
5
8
  module Formats
6
- class JsonFormat < Format
7
- def extension
8
- 'json'
9
- end
10
-
9
+ class JSONFormat < Format
11
10
  def mime_type
12
11
  'application/json'
13
12
  end
14
13
 
15
14
  def encode(hash, options = nil)
16
- ActiveSupport::JSON.encode(hash, options)
15
+ hash.to_json(options)
17
16
  end
18
17
 
19
18
  def decode(json)
20
- ActiveSupport::JSON.decode(json)
19
+ JSON.parse(json)
21
20
  end
22
21
  end
22
+
23
+ JsonFormat = JSONFormat
23
24
  end
24
25
  end
@@ -1,12 +1,13 @@
1
1
  require 'securerandom'
2
+ require 'tempfile'
2
3
 
3
4
  module Nestful
4
5
  module Formats
5
6
  class MultipartFormat < Format
6
7
  EOL = "\r\n"
7
-
8
+
8
9
  attr_reader :boundary, :stream
9
-
10
+
10
11
  def initialize(*args)
11
12
  super
12
13
  @boundary = SecureRandom.hex(10)
@@ -29,7 +30,7 @@ module Nestful
29
30
  def decode(body)
30
31
  body
31
32
  end
32
-
33
+
33
34
  protected
34
35
  def to_multipart(params, namespace = nil)
35
36
  params.each do |key, value|
@@ -43,14 +44,14 @@ module Nestful
43
44
 
44
45
  stream.write("--" + boundary + EOL)
45
46
 
46
- if value.is_a?(File) || value.is_a?(StringIO) || value.is_a?(Tempfile)
47
+ if value.respond_to?(:read)
47
48
  create_file_field(key, value)
48
49
  else
49
50
  create_field(key, value)
50
51
  end
51
- end
52
+ end
52
53
  end
53
-
54
+
54
55
  def create_file_field(key, value)
55
56
  stream.write(%Q{Content-Disposition: form-data; name="#{key}"; filename="#{filename(value)}"} + EOL)
56
57
  stream.write(%Q{Content-Type: application/octet-stream} + EOL)
@@ -61,14 +62,14 @@ module Nestful
61
62
  end
62
63
  stream.write(EOL)
63
64
  end
64
-
65
+
65
66
  def create_field(key, value)
66
67
  stream.write(%Q{Content-Disposition: form-data; name="#{key}"} + EOL)
67
68
  stream.write(EOL)
68
69
  stream.write(value)
69
70
  stream.write(EOL)
70
71
  end
71
-
72
+
72
73
  def filename(body)
73
74
  return body.original_filename if body.respond_to?(:original_filename)
74
75
  return File.basename(body.path) if body.respond_to?(:path)
@@ -1,32 +1,35 @@
1
1
  module Nestful
2
2
  module Formats
3
3
  class Format
4
- def extension
5
- end
6
-
7
4
  def mime_type
8
5
  end
9
-
6
+
10
7
  def encode(*args)
11
8
  end
12
-
9
+
13
10
  def decode(*args)
14
11
  end
15
12
  end
16
-
17
- autoload :BlankFormat, 'nestful/formats/blank_format'
18
- autoload :TextFormat, 'nestful/formats/text_format'
19
- autoload :MultipartFormat, 'nestful/formats/multipart_format'
20
- autoload :FormFormat, 'nestful/formats/form_format'
21
- autoload :XmlFormat, 'nestful/formats/xml_format'
22
- autoload :JsonFormat, 'nestful/formats/json_format'
23
-
13
+
14
+ MAPPING = {
15
+ 'application/json' => :json
16
+ }
17
+
18
+ def self.for(type)
19
+ format = MAPPING[type]
20
+ format && self[format]
21
+ end
22
+
24
23
  # Lookup the format class from a mime type reference symbol. Example:
25
24
  #
26
- # Nestful::Formats[:xml] # => Nestful::Formats::XmlFormat
27
25
  # Nestful::Formats[:json] # => Nestful::Formats::JsonFormat
28
26
  def self.[](mime_type_reference)
29
- Nestful::Formats.const_get(ActiveSupport::Inflector.camelize(mime_type_reference.to_s) + "Format")
27
+ Nestful::Formats.const_get(Helpers.camelize(mime_type_reference.to_s) + 'Format')
30
28
  end
31
- end
29
+ end
32
30
  end
31
+
32
+ Dir[File.dirname(__FILE__) + '/formats/*.rb'].sort.each do |path|
33
+ filename = File.basename(path)
34
+ require "nestful/formats/#{filename}"
35
+ end
@@ -0,0 +1,41 @@
1
+ require 'cgi'
2
+
3
+ module Nestful
4
+ module Helpers extend self
5
+ def to_query(key, value)
6
+ case key
7
+ when Hash
8
+ "#{CGI.escape(to_param(key))}=#{CGI.escape(to_param(value))}"
9
+
10
+ when Array
11
+ prefix = "#{key}[]"
12
+ value.collect { |v| to_query(prefix, v) }.join('&')
13
+
14
+ else
15
+ value
16
+ end
17
+ end
18
+
19
+ def to_param(object, namespace = nil)
20
+ case object
21
+ when Hash
22
+ object.map do |key, value|
23
+ key = "#{namespace}[#{key}]" if namespace
24
+ to_query(key, value)
25
+ end.join('&')
26
+
27
+ when Array
28
+ object.each do |value|
29
+ to_param(value)
30
+ end.join('/')
31
+
32
+ else
33
+ value
34
+ end
35
+ end
36
+
37
+ def camelize(value)
38
+ value.to_s.split('_').map {|w| w.capitalize }.join
39
+ end
40
+ end
41
+ end
@@ -1,22 +1,16 @@
1
1
  module Nestful
2
2
  class Request
3
- def self.callbacks(type = nil) #:nodoc:
4
- @callbacks ||= {}
5
- return @callbacks unless type
6
- @callbacks[type] ||= []
7
- end
8
-
9
3
  attr_reader :options, :format, :url
10
- attr_accessor :params, :body, :buffer, :method, :headers, :callbacks, :raw, :extension
11
-
4
+ attr_accessor :params, :body, :method, :headers
5
+
12
6
  # Connection options
13
7
  attr_accessor :proxy, :user, :password, :auth_type, :timeout, :ssl_options
14
-
8
+
15
9
  def initialize(url, options = {})
16
10
  @url = url.to_s
17
-
11
+
18
12
  @options = options
19
- @options.each do |key, val|
13
+ @options.each do |key, val|
20
14
  method = "#{key}="
21
15
  send(method, val) if respond_to?(method)
22
16
  end
@@ -24,127 +18,110 @@ module Nestful
24
18
  self.method ||= :get
25
19
  self.params ||= {}
26
20
  self.headers ||= {}
27
- self.body ||= ''
28
- self.format ||= :blank
21
+ self.format ||= :form
29
22
  end
30
-
23
+
31
24
  def format=(mime_or_format)
32
25
  @format = mime_or_format.is_a?(Symbol) ?
33
- Formats[mime_or_format].new : mime_or_format
34
- end
35
-
36
- def connection
37
- conn = Connection.new(uri, format)
38
- conn.proxy = proxy if proxy
39
- conn.user = user if user
40
- conn.password = password if password
41
- conn.auth_type = auth_type if auth_type
42
- conn.timeout = timeout if timeout
43
- conn.ssl_options = ssl_options if ssl_options
44
- conn
26
+ Formats[mime_or_format] : mime_or_format
45
27
  end
46
-
28
+
47
29
  def url=(value)
48
30
  @url = value
49
31
  @uri = nil
50
32
  @url
51
33
  end
52
-
34
+
53
35
  def uri
54
36
  return @uri if @uri
55
-
37
+
56
38
  url = @url.match(/^http/) ? @url : "http://#{@url}"
57
-
39
+
58
40
  @uri = URI.parse(url)
59
41
  @uri.path = "/" if @uri.path.empty?
60
-
61
- if extension
62
- extension = format.extension if extension.is_a?(Boolean)
63
- @uri.path += ".#{extension}"
64
- end
65
-
42
+
66
43
  @uri.query.split("&").each do |res|
67
44
  key, value = res.split("=")
68
45
  @params[key] = value
69
46
  end if @uri.query
70
-
47
+
71
48
  @uri
72
49
  end
73
-
50
+
74
51
  def path
75
52
  uri.path
76
53
  end
77
-
78
- def query_path
79
- query_path = path
80
- if params.any?
81
- query_path += "?"
82
- query_path += params.to_param
83
- end
84
- query_path
85
- end
86
-
54
+
87
55
  def execute
88
- callback(:before_request, self)
89
56
  result = nil
90
- if [:post, :put].include?(method)
91
- connection.send(method, path, encoded, headers) do |res|
92
- result = decoded(res)
93
- result.class_eval { attr_accessor :response }
94
- result.response = res
57
+
58
+ if encoded?
59
+ connection.send(method, path, encoded, build_headers) do |res|
60
+ result = res
95
61
  end
96
62
  else
97
- connection.send(method, query_path, headers) do |res|
98
- result = decoded(res)
99
- result.class_eval { attr_accessor :response }
100
- result.response = res
63
+ connection.send(method, query_path, build_headers) do |res|
64
+ result = res
101
65
  end
102
66
  end
103
- callback(:after_request, self, result)
104
- result
67
+
68
+ Response.new(result)
69
+
105
70
  rescue Redirection => error
106
71
  self.url = error.response['Location']
107
72
  execute
108
73
  end
109
-
74
+
110
75
  protected
111
- def encoded
112
- params.any? ? format.encode(params) : body
113
- end
114
76
 
115
- def decoded(result)
116
- if buffer
117
- data = Tempfile.new("nfr.#{rand(1000)}")
118
- size = 0
119
- total = result.content_length
120
-
121
- result.read_body do |chunk|
122
- callback(:progress, self, total, size += chunk.size)
123
- data.write(chunk)
124
- end
125
-
126
- data.rewind
127
- data
128
- else
129
- return result if raw
130
- data = result.body
131
- format ? format.decode(data) : data
132
- end
77
+ def connection
78
+ Connection.new(uri,
79
+ :proxy => proxy,
80
+ :timeout => timeout,
81
+ :ssl_options => ssl_options
82
+ )
83
+ end
84
+
85
+ def content_type_headers
86
+ if encoded?
87
+ {'Content-Type' => format.mime_type}
88
+ else
89
+ {}
133
90
  end
134
-
135
- def callbacks(type = nil)
136
- @callbacks ||= {}
137
- return @callbacks unless type
138
- @callbacks[type] ||= []
91
+ end
92
+
93
+ def auth_headers
94
+ if auth_type == :bearer
95
+ { 'Authorization' => "Bearer #{@password}" }
96
+ elsif auth_type == :basic
97
+ { 'Authorization' => 'Basic ' + ["#{@user}:#{@password}"].pack('m').delete("\r\n") }
98
+ else
99
+ { }
139
100
  end
140
-
141
- def callback(type, *args)
142
- procs = self.class.callbacks(type) + callbacks(type)
143
- procs.compact.each {|c| c.call(*args) }
101
+ end
102
+
103
+ def build_headers
104
+ auth_headers
105
+ .merge(content_type_headers)
106
+ .merge(headers)
107
+ end
108
+
109
+ def query_path
110
+ query_path = path
111
+
112
+ if params.any?
113
+ query_path += '?' + Helpers.to_param(params)
144
114
  end
115
+
116
+ query_path
117
+ end
118
+
119
+ def encoded?
120
+ [:post, :put].include?(method)
121
+ end
122
+
123
+ def encoded
124
+ params.any? ? format.encode(params) : body
125
+ end
145
126
  end
146
-
147
- class Request
148
- include Callbacks
149
- end
150
- end
127
+ end