restfulie 0.6.0 → 0.7.0

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.
Files changed (74) hide show
  1. data/README.textile +83 -7
  2. data/Rakefile +98 -13
  3. data/lib/restfulie/client/base.rb +48 -53
  4. data/lib/restfulie/client/configuration.rb +69 -0
  5. data/lib/restfulie/client/http/adapter.rb +487 -0
  6. data/lib/restfulie/client/http/atom_ext.rb +87 -0
  7. data/lib/restfulie/client/http/cache.rb +28 -0
  8. data/lib/restfulie/client/http/error.rb +80 -0
  9. data/lib/restfulie/client/http/marshal.rb +147 -0
  10. data/lib/restfulie/client/http.rb +13 -0
  11. data/lib/restfulie/client.rb +8 -56
  12. data/lib/restfulie/common/builder/builder_base.rb +58 -0
  13. data/lib/restfulie/common/builder/helpers.rb +22 -0
  14. data/lib/restfulie/common/builder/marshalling/atom.rb +197 -0
  15. data/lib/restfulie/common/builder/marshalling/base.rb +12 -0
  16. data/lib/restfulie/common/builder/marshalling/json.rb +2 -0
  17. data/lib/restfulie/common/builder/marshalling.rb +16 -0
  18. data/lib/restfulie/common/builder/rules/collection_rule.rb +10 -0
  19. data/lib/restfulie/common/builder/rules/link.rb +20 -0
  20. data/lib/restfulie/common/builder/rules/links.rb +9 -0
  21. data/lib/restfulie/common/builder/rules/member_rule.rb +8 -0
  22. data/lib/restfulie/common/builder/rules/namespace.rb +25 -0
  23. data/lib/restfulie/common/builder/rules/rules_base.rb +76 -0
  24. data/lib/restfulie/common/builder.rb +16 -0
  25. data/lib/restfulie/common/errors.rb +9 -0
  26. data/lib/restfulie/{logger.rb → common/logger.rb} +3 -5
  27. data/lib/restfulie/common/representation/atom.rb +48 -0
  28. data/lib/restfulie/common/representation/generic.rb +33 -0
  29. data/lib/restfulie/common/representation/xml.rb +24 -0
  30. data/lib/restfulie/common/representation.rb +10 -0
  31. data/lib/restfulie/common.rb +23 -0
  32. data/lib/restfulie/server/action_controller/base.rb +31 -0
  33. data/lib/restfulie/server/action_controller/params_parser.rb +62 -0
  34. data/lib/restfulie/server/action_controller/restful_responder.rb +39 -0
  35. data/lib/restfulie/server/action_controller/routing/restful_route.rb +14 -0
  36. data/lib/restfulie/server/action_controller/routing.rb +12 -0
  37. data/lib/restfulie/server/action_controller.rb +15 -0
  38. data/lib/restfulie/server/action_view/helpers.rb +45 -0
  39. data/lib/restfulie/server/action_view/template_handlers/tokamak.rb +15 -0
  40. data/lib/restfulie/server/action_view/template_handlers.rb +13 -0
  41. data/lib/restfulie/server/action_view.rb +8 -0
  42. data/lib/restfulie/server/configuration.rb +21 -0
  43. data/lib/restfulie/server/core_ext/array.rb +45 -0
  44. data/lib/restfulie/server/core_ext.rb +1 -0
  45. data/lib/restfulie/server/restfulie_controller.rb +1 -17
  46. data/lib/restfulie/server.rb +15 -0
  47. data/lib/restfulie.rb +4 -72
  48. data/lib/vendor/atom/configuration.rb +24 -0
  49. data/lib/vendor/atom/pub.rb +250 -0
  50. data/lib/vendor/atom/xml/parser.rb +373 -0
  51. data/lib/vendor/atom.rb +771 -0
  52. metadata +94 -33
  53. data/lib/restfulie/client/atom_media_type.rb +0 -75
  54. data/lib/restfulie/client/cache.rb +0 -103
  55. data/lib/restfulie/client/entry_point.rb +0 -94
  56. data/lib/restfulie/client/extensions/http.rb +0 -116
  57. data/lib/restfulie/client/helper.rb +0 -28
  58. data/lib/restfulie/client/instance.rb +0 -158
  59. data/lib/restfulie/client/request_execution.rb +0 -321
  60. data/lib/restfulie/client/state.rb +0 -36
  61. data/lib/restfulie/media_type.rb +0 -143
  62. data/lib/restfulie/media_type_control.rb +0 -115
  63. data/lib/restfulie/media_type_defaults.rb +0 -51
  64. data/lib/restfulie/server/atom_media_type.rb +0 -115
  65. data/lib/restfulie/server/base.rb +0 -91
  66. data/lib/restfulie/server/controller.rb +0 -122
  67. data/lib/restfulie/server/instance.rb +0 -102
  68. data/lib/restfulie/server/marshalling.rb +0 -47
  69. data/lib/restfulie/server/opensearch/description.rb +0 -54
  70. data/lib/restfulie/server/opensearch.rb +0 -18
  71. data/lib/restfulie/server/transition.rb +0 -93
  72. data/lib/restfulie/unmarshalling.rb +0 -131
  73. data/lib/vendor/jeokkarak/hashi.rb +0 -65
  74. data/lib/vendor/jeokkarak/jeokkarak.rb +0 -81
@@ -0,0 +1,487 @@
1
+ module Restfulie::Client::HTTP #:nodoc:
2
+
3
+ #=Response
4
+ # Default response class
5
+ class Response
6
+
7
+ attr_reader :method
8
+ attr_reader :path
9
+ attr_reader :code
10
+ attr_reader :body
11
+ attr_reader :headers
12
+ attr_reader :request
13
+
14
+ def initialize(method, path, code, body, headers, request)
15
+ @method = method
16
+ @path = path
17
+ @code = code
18
+ @body = body
19
+ @headers = headers
20
+ @request = request
21
+ end
22
+
23
+ def parse
24
+ self
25
+ end
26
+
27
+ end
28
+
29
+ #=ResponseHandler
30
+ # You can change instance registering a class according to the code.
31
+ #
32
+ #==Example
33
+ #
34
+ # class RequestExecutor
35
+ # include RequestAdapter
36
+ # def initialize(host)
37
+ # self.host=host
38
+ # end
39
+ # end
40
+ #
41
+ # class FakeResponse < Restfulie::Client::HTTP::Response
42
+ # end
43
+ #
44
+ # Restfulie::Client::HTTP::ResponseHandler.register(201,FakeResponse)
45
+ # @re = Restfulie::Client::HTTP::RequestExecutor.new('http://restfulie.com')
46
+ # puts @re.as('application/atom+xml').get!('/posts').class.to_i #=> FakeResponse
47
+ #
48
+ module ResponseHandler
49
+
50
+ @@response_handlers = {}
51
+ ##
52
+ # :singleton-method:
53
+ # Response handlers attribute reader
54
+ # * code: HTTP status code
55
+ def self.handlers(code)
56
+ @@response_handlers[code]
57
+ end
58
+
59
+ ##
60
+ # :singleton-method:
61
+ # Use to register response handlers
62
+ #
63
+ # * <tt>code: HTTP status code</tt>
64
+ # * <tt>response_class: Response class</tt>
65
+ #
66
+ #==Example:
67
+ # class FakeResponse < ::Restfulie::Client::HTTP::Response
68
+ # end
69
+ #
70
+ # Restfulie::Client::HTTP::ResponseHandler.register(200,FakeResponse)
71
+ #
72
+ def self.register(code,response_class)
73
+ @@response_handlers[code] = response_class
74
+ end
75
+
76
+ ##
77
+ # :singleton-method:
78
+ # Request Adapter uses this method to choose response instance
79
+ #
80
+ # *<tt>method: :get,:post,:delete,:head,:put</tt>
81
+ # *<tt>path: '/posts'</tt>
82
+ # *<tt>http_response</tt>
83
+ #
84
+ def self.handle(method, path, http_response, request)
85
+ response_class = @@response_handlers[http_response.code.to_i] || Response
86
+ headers = {}
87
+ http_response.header.each { |k, v| headers[k] = v }
88
+ response_class.new( method, path, http_response.code.to_i, http_response.body, headers, request)
89
+ end
90
+
91
+ end
92
+
93
+ #
94
+ # Request Adapter provides a minimal interface to exchange information between server over HTTP protocol through simple adapters.
95
+ #
96
+ # All the concrete adapters follow the interface laid down in this module.
97
+ # Default connection provider is net/http
98
+ #
99
+ #==Example
100
+ #
101
+ # @re = ::Restfulie::Client::HTTP::RequestExecutor.new('http://restfulie.com') #this class includes RequestAdapter module.
102
+ # puts @re.as('application/atom+xml').get!('/posts').title #=> 'Hello World!'
103
+ #
104
+ module RequestAdapter
105
+
106
+ attr_reader :host
107
+ attr_accessor :cookies
108
+ attr_writer :default_headers
109
+
110
+ def host=(host)
111
+ if host.is_a?(::URI)
112
+ @host = host
113
+ else
114
+ @host = ::URI.parse(host)
115
+ end
116
+ end
117
+
118
+ def default_headers
119
+ @default_headers ||= {}
120
+ end
121
+
122
+ #GET HTTP verb without {Error}
123
+ # * <tt>path: '/posts'</tt>
124
+ # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
125
+ def get(path, *args)
126
+ request(:get, path, *args)
127
+ end
128
+
129
+ #HEAD HTTP verb without {Error}
130
+ # * <tt>path: '/posts'</tt>
131
+ # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
132
+ def head(path, *args)
133
+ request(:head, path, *args)
134
+ end
135
+
136
+ #POST HTTP verb without {Error}
137
+ # * <tt>path: '/posts'</tt>
138
+ # * <tt>payload: 'some text'</tt>
139
+ # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
140
+ def post(path, payload, *args)
141
+ request(:post, path, payload, *args)
142
+ end
143
+
144
+ #PUT HTTP verb without {Error}
145
+ # * <tt>path: '/posts'</tt>
146
+ # * <tt>payload: 'some text'</tt>
147
+ # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
148
+ def put(path, payload, *args)
149
+ request(:put, path, payload, *args)
150
+ end
151
+
152
+ #DELETE HTTP verb without {Error}
153
+ # * <tt>path: '/posts'</tt>
154
+ # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
155
+ def delete(path, *args)
156
+ request(:delete, path, *args)
157
+ end
158
+
159
+ #GET HTTP verb {Error}
160
+ # * <tt>path: '/posts'</tt>
161
+ # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
162
+ def get!(path, *args)
163
+ request!(:get, path, *args)
164
+ end
165
+
166
+ #HEAD HTTP verb {Error}
167
+ # * <tt>path: '/posts'</tt>
168
+ # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
169
+ def head!(path, *args)
170
+ request!(:head, path, *args)
171
+ end
172
+
173
+ #POST HTTP verb {Error}
174
+ # * <tt>path: '/posts'</tt>
175
+ # * <tt>payload: 'some text'</tt>
176
+ # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
177
+ def post!(path, payload, *args)
178
+ request!(:post, path, payload, *args)
179
+ end
180
+
181
+ #PUT HTTP verb {Error}
182
+ # * <tt>path: '/posts'</tt>
183
+ # * <tt>payload: 'some text'</tt>
184
+ # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
185
+ def put!(path, payload, *args)
186
+ request!(:put, path, payload, *args)
187
+ end
188
+
189
+ #DELETE HTTP verb {Error}
190
+ # * <tt>path: '/posts'</tt>
191
+ # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
192
+ def delete!(path, *args)
193
+ request!(:delete, path, *args)
194
+ end
195
+
196
+ #Executes a request against your server and return a response instance without {Error}
197
+ # * <tt>method: :get,:post,:delete,:head,:put</tt>
198
+ # * <tt>path: '/posts'</tt>
199
+ # * <tt>args: payload: 'some text' and/or headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
200
+ def request(method, path, *args)
201
+ request!(method, path, *args)
202
+ rescue Error::RESTError => se
203
+ se.response
204
+ end
205
+
206
+ #Executes a request against your server and return a response instance.
207
+ # * <tt>method: :get,:post,:delete,:head,:put</tt>
208
+ # * <tt>path: '/posts'</tt>
209
+ # * <tt>args: payload: 'some text' and/or headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
210
+ def request!(method, path, *args)
211
+ headers = default_headers.merge(args.extract_options!)
212
+ unless @host.user.blank? && @host.password.blank?
213
+ headers["Authorization"] = "Basic " + ["#{@host.user}:#{@host.password}"].pack("m").delete("\r\n")
214
+ end
215
+ headers['cookie'] = @cookies if @cookies
216
+ args << headers
217
+
218
+ ::Restfulie::Common::Logger.logger.info(request_to_s(method, path, *args)) if ::Restfulie::Common::Logger.logger
219
+ begin
220
+ connection = get_connection_provider.send(method, path, *args)
221
+ response = ResponseHandler.handle(method, path, connection, self).parse
222
+ rescue Exception => e
223
+ raise Error::ServerNotAvailableError.new(self, Response.new(method, path, 503, nil, {}, self), e )
224
+ end
225
+
226
+ case response.code
227
+ when 100..299
228
+ response
229
+ when 300..399
230
+ raise Error::Redirection.new(self, response)
231
+ when 400
232
+ raise Error::BadRequest.new(self, response)
233
+ when 401
234
+ raise Error::Unauthorized.new(self, response)
235
+ when 403
236
+ raise Error::Forbidden.new(self, response)
237
+ when 404
238
+ raise Error::NotFound.new(self, response)
239
+ when 405
240
+ raise Error::MethodNotAllowed.new(self, response)
241
+ when 407
242
+ raise Error::ProxyAuthenticationRequired.new(self, response)
243
+ when 409
244
+ raise Error::Conflict.new(self, response)
245
+ when 410
246
+ raise Error::Gone.new(self, response)
247
+ when 412
248
+ raise Error::PreconditionFailed.new(self, response)
249
+ when 402, 406, 408, 411, 413..499
250
+ raise Error::ClientError.new(self, response)
251
+ when 501
252
+ raise Error::NotImplemented.new(self, response)
253
+ when 500, 502..599
254
+ raise Error::ServerError.new(self, response)
255
+ else
256
+ raise Error::UnknownError.new(self, response)
257
+ end
258
+ end
259
+
260
+ private
261
+
262
+ def get_connection_provider
263
+ @connection ||= ::Net::HTTP.new(@host.host, @host.port)
264
+ end
265
+
266
+ protected
267
+
268
+ def request_to_s(method, path, *args)
269
+ result = ["#{method.to_s.upcase} #{path}"]
270
+
271
+ arguments = args.dup
272
+ headers = arguments.extract_options!
273
+
274
+ if [:post, :put].include?(method)
275
+ body = arguments.shift
276
+ end
277
+
278
+ result << headers.collect { |key, value| "#{key}: #{value}" }.join("\n")
279
+
280
+ (result + [body ? (body.inspect + "\n") : nil]).compact.join("\n") << "\n"
281
+ end
282
+
283
+ end
284
+
285
+ #=RequestBuilder
286
+ # Uses RequestAdapater to create a HTTP Request DSL
287
+ #
288
+ #==Example:
289
+ #
290
+ # @builder = ::Restfulie::Client::HTTP::RequestBuilderExecutor.new("http://restfulie.com") #this class includes RequestBuilder module.
291
+ # @builder.at('/posts').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200
292
+ #
293
+ module RequestBuilder
294
+ include RequestAdapter
295
+
296
+ #Set host
297
+ def at(url)
298
+ self.host = url
299
+ self
300
+ end
301
+
302
+ #Set Content-Type and Accept headers
303
+ def as(content_type)
304
+ headers['Content-Type'] = content_type
305
+ accepts(content_type)
306
+ end
307
+
308
+ #Set Accept headers
309
+ def accepts(content_type)
310
+ headers['Accept'] = content_type
311
+ self
312
+ end
313
+
314
+ #
315
+ #Merge internal header
316
+ #
317
+ # * <tt>headers (e.g. {'Cache-control' => 'no-cache'})</tt>
318
+ #
319
+ def with(headers)
320
+ self.headers.merge!(headers)
321
+ self
322
+ end
323
+
324
+ def headers
325
+ @headers || @headers = {}
326
+ end
327
+
328
+ #Path (e.g. http://restfulie.com/posts => /posts)
329
+ def path
330
+ host.path
331
+ end
332
+
333
+ def get
334
+ request(:get, path, headers)
335
+ end
336
+
337
+ def head
338
+ request(:head, path, headers)
339
+ end
340
+
341
+ def post(payload)
342
+ request(:post, path, payload, headers)
343
+ end
344
+
345
+ def put(payload)
346
+ request(:put, path, payload, headers)
347
+ end
348
+
349
+ def delete
350
+ request(:delete, path, headers)
351
+ end
352
+
353
+ def get!
354
+ request!(:get, path, headers)
355
+ end
356
+
357
+ def head!
358
+ request!(:head, path, headers)
359
+ end
360
+
361
+ def post!(payload)
362
+ request!(:post, path, payload, headers)
363
+ end
364
+
365
+ def put!(payload)
366
+ request!(:put, path, payload, headers)
367
+ end
368
+
369
+ def delete!
370
+ request!(:delete, path, headers)
371
+ end
372
+
373
+ protected
374
+
375
+ def headers=(h)
376
+ @headers = h
377
+ end
378
+
379
+ end
380
+
381
+ #=RequestHistory
382
+ # Uses RequestBuilder and remind previous requests
383
+ #
384
+ #==Example:
385
+ #
386
+ # @executor = ::Restfulie::Client::HTTP::RequestHistoryExecutor.new("http://restfulie.com") #this class includes RequestHistory module.
387
+ # @executor.at('/posts').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200 #first request
388
+ # @executor.at('/blogs').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200 #second request
389
+ # @executor.request_history!(0) #doing first request again
390
+ #
391
+ module RequestHistory
392
+ include RequestBuilder
393
+
394
+ attr_accessor_with_default :max_to_remind, 10
395
+
396
+ def snapshots
397
+ @snapshots || @snapshots = []
398
+ end
399
+
400
+ def request!(method=nil, path=nil, *args)#:nodoc:
401
+ if method == nil || path == nil
402
+ raise 'History not selected' unless @snapshot
403
+ super( @snapshot[:method], @snapshot[:path], *@snapshot[:args] )
404
+ else
405
+ @snapshot = make_snapshot(method, path, *args)
406
+ unless snapshots.include?(@snapshot)
407
+ snapshots.shift if snapshots.size >= max_to_remind
408
+ snapshots << @snapshot
409
+ end
410
+ super
411
+ end
412
+ end
413
+
414
+ def request(method=nil, path=nil, *args)#:nodoc:
415
+ request!(method, path, *args)
416
+ rescue Error::RESTError => se
417
+ se.response
418
+ end
419
+
420
+ def history(number)
421
+ @snapshot = snapshots[number]
422
+ raise "Undefined snapshot for #{number}" unless @snapshot
423
+ self.host = @snapshot[:host]
424
+ self.cookies = @snapshot[:cookies]
425
+ self.headers = @snapshot[:headers]
426
+ self.default_headers = @snapshot[:default_headers]
427
+ at(@snapshot[:path])
428
+ end
429
+
430
+ private
431
+
432
+ def make_snapshot(method, path, *args)
433
+ arguments = args.dup
434
+ cutom_headers = arguments.extract_options!
435
+ { :host => self.host.dup,
436
+ :default_headers => self.default_headers.dup,
437
+ :headers => self.headers.dup,
438
+ :cookies => self.cookies,
439
+ :method => method,
440
+ :path => path,
441
+ :args => arguments << self.headers.merge(cutom_headers) }
442
+ end
443
+
444
+ end
445
+
446
+ #=This class includes RequestAdapter module.
447
+ class RequestExecutor
448
+ include RequestAdapter
449
+
450
+ # * <tt> host (e.g. 'http://restfulie.com') </tt>
451
+ # * <tt> default_headers (e.g. {'Cache-control' => 'no-cache'} ) </tt>
452
+ def initialize(host, default_headers = {})
453
+ self.host=host
454
+ self.default_headers=default_headers
455
+ end
456
+
457
+ end
458
+
459
+ #=This class includes RequestBuilder module.
460
+ class RequestBuilderExecutor
461
+ include RequestBuilder
462
+
463
+ # * <tt> host (e.g. 'http://restfulie.com') </tt>
464
+ # * <tt> default_headers (e.g. {'Cache-control' => 'no-cache'} ) </tt>
465
+ def initialize(host, default_headers = {})
466
+ self.host=host
467
+ self.default_headers=default_headers
468
+ end
469
+ def host=(host)
470
+ super
471
+ at(self.host.path)
472
+ end
473
+ def at(path)
474
+ @path = path
475
+ self
476
+ end
477
+ def path
478
+ @path
479
+ end
480
+ end
481
+
482
+ #=This class inherits RequestBuilderExecutor and include RequestHistory module.
483
+ class RequestHistoryExecutor < RequestBuilderExecutor
484
+ include RequestHistory
485
+ end
486
+
487
+ end
@@ -0,0 +1,87 @@
1
+ module Restfulie::Client::HTTP#:nodoc:
2
+
3
+ # Offer easy access to Atom link relationships, such as <tt>post.next</tt> for
4
+ # <tt>&lt;link rel="next" href="http://resource.entrypoint.com/post/12" type="application/atom+xml" /&gt;</tt> relationships.
5
+ module AtomLinkShortcut
6
+ def method_missing(method_sym,*args)#:nodoc:
7
+ selected_links = links.select{ |l| l.rel == method_sym.to_s }
8
+ super if selected_links.empty?
9
+ link = (selected_links.size == 1) ? selected_links.first : selected_links
10
+
11
+ return link unless link.instance_variable_defined?(:@type)
12
+ link.accepts(link.type)
13
+
14
+ representation = Restfulie::Client::HTTP::RequestMarshaller.content_type_for(link.type)
15
+ return representation.prepare_link_for(link) if representation
16
+ link
17
+
18
+ end
19
+ end
20
+
21
+ # Offers a way to access Atom entries element's in namespaced extensions.
22
+ module AtomElementShortcut
23
+ def method_missing(method_sym,*args)
24
+ return super(method_sym, *args) unless simple_extensions
25
+
26
+ found = find_extension_entry_for(method_sym)
27
+ return super(method_sym, *args) if found.empty?
28
+ result = found.collect do |pair|
29
+ pair.last.length==1 ? pair.last.first : pair.last
30
+ end
31
+ result.length==1 ? result.first : result
32
+ end
33
+
34
+ def respond_to?(method_sym)
35
+ return super(method_sym) unless simple_extensions
36
+
37
+ found = find_extension_entry_for(method_sym)
38
+ (found.length!=0) || super(method_sym)
39
+ end
40
+
41
+ private
42
+ def find_extension_entry_for(method_sym)
43
+ start = -(method_sym.to_s.length + 1)
44
+ found = simple_extensions.select do |k, v|
45
+ method_sym.to_s == k[start..-2]
46
+ end
47
+ end
48
+ end
49
+
50
+ # Offers a way to access Atom entries element's in namespaced extensions.
51
+ module AtomElementShortcut
52
+ def method_missing(method_sym,*args)
53
+ return super(method_sym, *args) unless simple_extensions
54
+
55
+ start = -(method_sym.to_s.length + 1)
56
+ found = simple_extensions.select do |k, v|
57
+ method_sym.to_s == k[start..-2]
58
+ end
59
+ return super(method_sym, *args) if found.empty?
60
+ result = found.collect do |pair|
61
+ pair.last.length==1 ? pair.last.first : pair.last
62
+ end
63
+ result.length==1 ? result.first : result
64
+ end
65
+ end
66
+
67
+ # Gives to Atom::Link capabilities to fetch related resources.
68
+ module LinkRequestBuilder
69
+ include RequestMarshaller
70
+ def path#:nodoc:
71
+ at(href)
72
+ super
73
+ end
74
+ end
75
+
76
+ # inject new behavior in rAtom instances to enable easily access to link relationships.
77
+ ::Atom::Feed.instance_eval {
78
+ include AtomLinkShortcut
79
+ include AtomElementShortcut
80
+ }
81
+ ::Atom::Entry.instance_eval {
82
+ include AtomLinkShortcut
83
+ include AtomElementShortcut
84
+ }
85
+ ::Atom::Link.instance_eval { include LinkRequestBuilder }
86
+
87
+ end
@@ -0,0 +1,28 @@
1
+ module Restfulie::Client::HTTP
2
+
3
+ module Cache
4
+
5
+ def store
6
+ @store || @store = ::ActiveSupport::Cache::MemoryStore.new
7
+ end
8
+
9
+ def get
10
+ store.fetch(@uri) do
11
+ request(:get, @uri, @headers)
12
+ end
13
+ end
14
+
15
+ def head
16
+ store.fetch(@uri) do
17
+ request(:head, @uri, @headers)
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ class RequestBuilderExecutorWithCache < RequestBuilderExecutor
24
+ include Cache
25
+ end
26
+
27
+ end
28
+
@@ -0,0 +1,80 @@
1
+ module Restfulie::Client::HTTP#:nodoc:
2
+
3
+ #Client errors
4
+ module Error
5
+
6
+ #Generic error class and superclass of all other errors raised by client restfulie
7
+ class BaseError < StandardError; end
8
+
9
+ class TranslationError < BaseError; end
10
+
11
+ # Standard error thrown on major client exceptions
12
+ class RESTError < StandardError
13
+
14
+ attr_reader :response
15
+ attr_reader :request
16
+
17
+ def initialize(request, response)
18
+ @request = request
19
+ @response = response
20
+ end
21
+
22
+ def to_s
23
+ "HTTP error #{@response.code} when invoking #{@request.host}#{::URI.decode(@response.path)} via #{@response.method}. " +
24
+ ((@response.body.blank?) ? "No additional data was sent." : "The complete response was:\n" + @response.body)
25
+ rescue
26
+ super
27
+ end
28
+
29
+ end
30
+
31
+ #Represents the HTTP code 503
32
+ class ServerNotAvailableError < RESTError
33
+ def initialize(request, response, exception)
34
+ super(request, response)
35
+ set_backtrace(exception.backtrace)
36
+ end
37
+ end
38
+
39
+ class UnknownError < RESTError; end
40
+
41
+ #Represents the HTTP code 300 range
42
+ class Redirection < RESTError; end
43
+
44
+ class ClientError < RESTError; end
45
+
46
+ #Represents the HTTP code 400
47
+ class BadRequest < ClientError; end
48
+
49
+ #Represents the HTTP code 401
50
+ class Unauthorized < ClientError; end
51
+
52
+ #Represents the HTTP code 403
53
+ class Forbidden < ClientError; end
54
+
55
+ #Represents the HTTP code 404
56
+ class NotFound < ClientError; end
57
+
58
+ #Represents the HTTP code 405
59
+ class MethodNotAllowed < ClientError; end
60
+
61
+ #Represents the HTTP code 412
62
+ class PreconditionFailed < ClientError; end
63
+
64
+ #Represents the HTTP code 407
65
+ class ProxyAuthenticationRequired < ClientError; end
66
+
67
+ #Represents the HTTP code 409
68
+ class Conflict < ClientError; end
69
+
70
+ #Represents the HTTP code 410
71
+ class Gone < ClientError; end
72
+
73
+ #Represents the HTTP code 500
74
+ class ServerError < RESTError; end
75
+
76
+ #Represents the HTTP code 501
77
+ class NotImplemented < ServerError; end
78
+ end
79
+ end
80
+