nestful 0.0.8 → 1.0.0.pre

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