restfulie 0.6.0 → 0.7.0

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