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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/README.markdown +11 -37
- data/Rakefile +1 -14
- data/examples/resource.rb +6 -0
- data/lib/nestful/.DS_Store +0 -0
- data/lib/nestful/connection.rb +107 -245
- data/lib/nestful/endpoint.rb +42 -0
- data/lib/nestful/exceptions.rb +1 -1
- data/lib/nestful/formats/form_format.rb +3 -3
- data/lib/nestful/formats/json_format.rb +10 -9
- data/lib/nestful/formats/multipart_format.rb +9 -8
- data/lib/nestful/formats.rb +19 -16
- data/lib/nestful/helpers.rb +41 -0
- data/lib/nestful/request.rb +73 -96
- data/lib/nestful/resource.rb +144 -27
- data/lib/nestful/response/headers.rb +31 -0
- data/lib/nestful/response.rb +47 -0
- data/lib/nestful/version.rb +3 -0
- data/lib/nestful.rb +17 -30
- data/nestful.gemspec +14 -54
- metadata +21 -28
- data/lib/nestful/formats/blank_format.rb +0 -13
- data/lib/nestful/formats/text_format.rb +0 -17
- data/lib/nestful/formats/xml_format.rb +0 -34
- data/lib/nestful/oauth.rb +0 -24
- data/lib/nestful/request/callbacks.rb +0 -28
@@ -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
|
data/lib/nestful/exceptions.rb
CHANGED
@@ -1,24 +1,25 @@
|
|
1
|
-
|
2
|
-
require '
|
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
|
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
|
-
|
15
|
+
hash.to_json(options)
|
17
16
|
end
|
18
17
|
|
19
18
|
def decode(json)
|
20
|
-
|
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.
|
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)
|
data/lib/nestful/formats.rb
CHANGED
@@ -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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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(
|
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
|
data/lib/nestful/request.rb
CHANGED
@@ -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, :
|
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.
|
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]
|
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
|
-
|
91
|
-
|
92
|
-
|
93
|
-
result
|
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,
|
98
|
-
result =
|
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
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
@
|
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
|
-
|
142
|
-
|
143
|
-
|
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
|